# decorators.py
import asyncio
import json
import logging
import time
from datetime import datetime
from functools import wraps
from typing import Any, Callable, Dict, Optional, Tuple
from urllib.parse import parse_qs

from fastapi import Request
from jose import JWTError, jwt
from user_agents import parse

from app.core import settings
from app.database.db import auto_commit_session
from app.models.system import SysLog
from app.utils.ip_utils import get_client_ip, get_ip_geolocation_info

# 创建日志记录器
logger = logging.getLogger(__name__)

# 常量定义
SENSITIVE_KEYS = {"password", "token", "secret", "key", "authorization", "cookie"}
MAX_PARAMS_LENGTH = 1000
MAX_RESPONSE_LENGTH = 2000


def __decode_token(token: str, verify_flag: bool = False) -> dict:
    """
    解码JWT令牌

    Args:
        token: JWT令牌字符串
        verify_flag: 是否验证签名，默认为False

    Returns:
        dict: 解码后的payload字典，解码失败返回空字典
    """
    try:
        payload = jwt.decode(
            token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM], options={"verify_exp": verify_flag}
        )
        return payload
    except JWTError:
        return {}


def _parse_user_agent(user_agent: str) -> Tuple[str, str, str]:
    """
    解析浏览器用户代理字符串

    Args:
        user_agent: 用户代理字符串

    Returns:
        tuple: (浏览器名称, 浏览器版本, 操作系统)
    """
    try:
        parsed_agent = parse(user_agent)
        browser = parsed_agent.browser.family or "Unknown"
        browser_version = parsed_agent.browser.version_string or "Unknown"
        os = parsed_agent.os.family or "Unknown"
        return browser, browser_version, os
    except Exception:
        return "Unknown", "Unknown", "Unknown"


def _get_user_info_from_request(request: Request) -> Optional[int]:
    """
    从请求中获取用户信息

    Args:
        request: FastAPI 请求对象

    Returns:
        Optional[int]: 用户ID，未找到返回None
    """
    auth_header = request.headers.get("Authorization")
    if not auth_header:
        return None

    token = auth_header.replace("Bearer ", "")
    payload = __decode_token(token) if token else {}
    return payload.get("user_id")


def _filter_sensitive_data(data: Dict[str, Any]) -> Dict[str, Any]:
    """
    过滤敏感数据，将敏感字段值替换为星号

    Args:
        data: 原始数据字典

    Returns:
        Dict[str, Any]: 过滤后的安全数据
    """
    if not isinstance(data, dict):
        return data

    filtered_data = {}
    for key, value in data.items():
        key_lower = key.lower()
        # 检查是否为敏感字段
        if any(sensitive in key_lower for sensitive in SENSITIVE_KEYS):
            filtered_data[key] = "******"
        elif isinstance(value, dict):
            filtered_data[key] = _filter_sensitive_data(value)
        elif isinstance(value, list):
            filtered_data[key] = [_filter_sensitive_data(item) if isinstance(item, dict) else item for item in value]
        else:
            filtered_data[key] = value
    return filtered_data


async def _collect_request_params(request: Request) -> Dict[str, Any]:
    """
    收集请求参数，包含查询参数、路径参数和请求体

    Args:
        request: FastAPI 请求对象

    Returns:
        Dict[str, Any]: 包含各类参数的字典
    """
    params = {}

    # 查询参数
    if request.query_params:
        params["query"] = dict(request.query_params)

    # 路径参数
    if hasattr(request, "path_params") and request.path_params:
        params["path"] = dict(request.path_params)

    # 请求体（仅非GET方法）
    if request.method.upper() != "GET":
        try:
            content_type = request.headers.get("content-type", "").lower()

            if "multipart/form-data" in content_type:
                # 处理文件上传
                form = await request.form()
                body_info = {}
                for key, value in form.items():
                    if hasattr(value, "filename") and value.filename:
                        body_info[key] = {
                            "type": "file",
                            "filename": value.filename,
                            "content_type": getattr(value, "content_type", None),
                            "size": getattr(value, "size", None),
                        }
                    else:
                        body_info[key] = {
                            "type": "field",
                            "value": value,
                        }
                params["body"] = body_info
            else:
                # 处理其他内容类型
                body_raw = await request.body()
                if body_raw:
                    if "application/json" in content_type:
                        try:
                            params["body"] = json.loads(body_raw)
                        except json.JSONDecodeError:
                            params["body"] = body_raw.decode("utf-8", errors="replace")
                    elif "application/x-www-form-urlencoded" in content_type:
                        parsed = parse_qs(body_raw.decode("utf-8"))
                        params["body"] = {k: v[0] if len(v) == 1 else v for k, v in parsed.items()}
                    else:
                        params["body"] = body_raw.decode("utf-8", errors="replace")
        except Exception as e:
            logger.warning(f"解析请求体失败: {str(e)}")
            params["body"] = f"<parse error: {str(e)}>"

    return _filter_sensitive_data(params)


async def _get_location_info(ip: str) -> Tuple[str, str]:
    """
    获取IP地址的地理位置信息

    Args:
        ip: IP地址

    Returns:
        Tuple[str, str]: (省份, 城市)
    """
    if not ip or ip == "Unknown":
        return "Unknown", "Unknown"

    try:
        location_info = get_ip_geolocation_info(ip)
        province = location_info.get("province", "Unknown")
        city = location_info.get("city", "Unknown")
        return province, city
    except Exception:
        return "Unknown", "Unknown"


async def save_api_log_to_db(log_data: Dict[str, Any]):
    """
    将API日志保存到数据库

    Args:
        log_data: 包含日志信息的字典
    """
    try:
        async with auto_commit_session() as session:
            log_entry = SysLog(**log_data)
            session.add(log_entry)
    except Exception as e:
        logger.error(f"保存日志到数据库失败: {str(e)}")


def _create_log_task(log_data: Dict[str, Any]) -> asyncio.Task:
    """
    创建后台日志保存任务

    Args:
        log_data: 日志数据字典

    Returns:
        asyncio.Task: 后台任务对象
    """
    return asyncio.create_task(save_api_log_to_db(log_data))


def log(
    content: str,
    module: Optional[str] = None,
    save_params: bool = True,
    save_result: bool = False,
    log_level: str = "INFO",
):
    """
    带参数的 API 日志装饰器

    Args:
        content: 日志内容
        module: 模块名称
        save_params: 是否保存请求参数
        save_result: 是否保存响应结果
        log_level: 日志级别（INFO, DEBUG, WARNING, ERROR）

    Returns:
        Callable: 装饰器函数
    """

    def decorator(func: Callable) -> Callable:
        @wraps(func)
        async def async_wrapper(*args, **kwargs):
            request: Optional[Request] = None
            start_time = time.time()
            error = None
            result = None

            # 提取 request 对象
            for arg in args:
                if isinstance(arg, Request):
                    request = arg
                    break
            if request is None:
                request = kwargs.get("request")
            if request is None:
                raise ValueError(f"路由函数 {func.__name__} 必须包含 'request: Request' 参数")

            # 收集请求信息
            method = request.method.upper()
            client_ip = get_client_ip(request)
            func_name = func.__name__

            # 异步获取地理位置信息（不阻塞主流程）
            province, city = await _get_location_info(client_ip)

            # 解析浏览器信息
            user_agent = request.headers.get("User-Agent", "")
            browser, browser_version, os = _parse_user_agent(user_agent)

            # 获取用户信息
            user_id = _get_user_info_from_request(request)

            # 收集请求参数（如果启用）
            params = {}
            if save_params:
                try:
                    params = await _collect_request_params(request)
                except Exception as e:
                    logger.warning(f"收集请求参数失败: {str(e)}")
                    params = {"error": str(e)}

            # 执行原函数
            try:
                result = await func(*args, **kwargs)
                return result
            except Exception as e:
                error = e
                log_level_actual = "ERROR"
                raise
            finally:
                duration_ms = int((time.time() - start_time) * 1000)
                log_level_actual = log_level_actual if error else log_level

                if hasattr(result, "model_dump") and callable(getattr(result, "model_dump")):
                    response_content = result.model_dump()
                elif hasattr(result, "dict") and callable(getattr(result, "dict")):
                    response_content = result.dict()
                elif hasattr(result, "__dict__"):
                    response_content = result.__dict__
                else:
                    response_content = str(result)

                if not user_id:
                    try:
                        # 修复：检查response_content是否为字典类型
                        if isinstance(response_content, dict):
                            # 修复：正确访问嵌套的access_token字段
                            token_data = response_content.get("data", {})
                            token = token_data.get("access_token")

                            if token:
                                payload = __decode_token(token, verify_flag=True)
                                user_id = payload.get("user_id")
                                logger.debug(f"从响应中成功提取用户ID: {user_id}")
                            else:
                                logger.debug("响应中未找到access_token")
                        else:
                            logger.debug(f"响应内容不是字典类型: {type(response_content)}")
                    except Exception as e:
                        logger.warning(f"从响应中提取用户ID失败: {str(e)}")

                # 准备日志数据
                log_data = {
                    "module": module,
                    "request_method": method,
                    "content": f"{content} - 失败" if error else content,
                    "request_params": json.dumps(params, ensure_ascii=False) if params and save_params else None,
                    "response_content": (
                        json.dumps(response_content, ensure_ascii=False, default=str)[:MAX_RESPONSE_LENGTH]
                        if response_content and save_result and not error
                        else None
                    ),
                    "request_uri": str(request.url),
                    "method": func_name,
                    "ip": client_ip,
                    "province": province,
                    "city": city,
                    "execution_time": duration_ms,
                    "browser": browser,
                    "browser_version": browser_version,
                    "os": os,
                    "create_by": user_id,
                    "create_time": datetime.now(),
                    "is_deleted": 0,
                }

                # 记录错误信息
                if error:
                    log_data["error_message"] = str(error)
                    log_data["error_type"] = type(error).__name__

                # 异步记录日志
                try:
                    _create_log_task(log_data)

                    # 同时输出到控制台（便于调试）
                    if log_level_actual == "ERROR":
                        logger.error(f"{module}.{func_name} - {content} - 耗时: {duration_ms}ms - 错误: {error}")
                    elif log_level_actual == "WARNING":
                        logger.warning(f"{module}.{func_name} - {content} - 耗时: {duration_ms}ms")
                    else:
                        logger.info(f"{module}.{func_name} - {content} - 耗时: {duration_ms}ms")

                except Exception as log_err:
                    logger.error(f"日志记录失败 {func.__name__}: {log_err}")

        if not asyncio.iscoroutinefunction(func):
            raise TypeError("仅支持异步路由函数")

        return async_wrapper

    return decorator
